﻿//Copyright Notice:  ©2009 Microsoft Corporation.  All rights reserved.
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.IO;
using System.Data;
using System.Collections.ObjectModel;

namespace Microsoft.InformationSecurity.CISF.Security.AuthZServices
{
    /// <summary>
    /// A collection of RoleMember objects.
    /// </summary>
    public class RoleMembersEntries : Collection<RoleMembersEntry>
    {
        #region Private Variables
        private Collection<string> roleFilters;

        #endregion

        #region Constructor
        /// <summary>
        /// Contains all application Roles and related groups.  Inherits Collection(Entry).
        /// </summary>
        public RoleMembersEntries()
        {
        }

        #endregion

        #region Methods

        #region Add
        /// <summary>
        /// Adds the Property object to the Entry and the Entry object to the collection of Entry objects
        /// if the Entry object doesn't already exist.
        /// </summary>
        /// <param name="entryName">Name to the Entry object.</param>
        /// <param name="property">The Property object to add to the Entry object.</param>
        /// <param name="noDuplicates">
        /// true - does not allow values found in the Property object to be added to 
        /// Entry if the Entry already contains a Property object of the same time and that
        /// Property object already contains the property value.
        /// </param>
        public void Add(string entryName, Property property, bool noDuplicates)
        {
            //First determine if the entry already exist for this name.
            //If it does exist, add the property to the entry.
            //If not, create a new entry and attach the property to it, then add to this.

            RoleMembersEntry entry;
            if ((entry = this.GetEntry(entryName)) != null)
                entry.Add(property, noDuplicates);
            else
            {
                entry = new RoleMembersEntry(entryName);
                entry.Add(property, noDuplicates);
                this.Add(entry);
            }
        }

        #endregion

        #region Clear
        /// <summary>
        /// Clear out all collections.
        /// </summary>
        private void ClearAll()
        {
            if (this != null)
                this.Clear();
        }

        #endregion

        #region Contains
        /// <summary>
        /// Determines if the Entry object exists in the collection of Entry objects.
        /// </summary>
        /// <param name="entry">An Entry object.</param>
        /// <returns>
        /// true - the entry object exists in the collection of Entry objects.
        /// </returns>
        public new bool Contains(RoleMembersEntry entry)
        {
            bool retVal = false;
            foreach (RoleMembersEntry checkEntry in this)
                if (checkEntry.Name == entry.Name)
                {
                    retVal = true;
                    break;
                }
            return retVal;
        }

        #endregion

        #region GetEntry
        /// <summary>
        /// Finds the entry in the Entry collection with the given name.
        /// </summary>
        /// <param name="name">The name of the entry.</param>
        /// <returns>A RoleMembersEntry object</returns>
        public RoleMembersEntry GetEntry(string name)
        {
            RoleMembersEntry retEntry = null;

            foreach (RoleMembersEntry entry in this)
                if (entry.Name == name)
                    retEntry = entry;

            return retEntry;
        }

        #endregion

        #region GetGroupsByRolesDB
        /// <summary>
        /// Load the roles and their related group from the application database.
        /// </summary>
        /// <param name="dbKey">Name of the configuration key to use to get database connection string.</param>
        private void GetGroupsByRolesDB(string dbKey)
        {
            Collection<SqlParameter> sqlParameters = new Collection<SqlParameter>();
             
            sqlParameters.Add(new SqlParameter("@role", SqlDbType.NVarChar, 4000, AuthZDataAccess.FormatFilter(this.roleFilters)));

            //For each user returned by the query.
            foreach (object[] columns in AuthZDataAccess.GetSqlDataReader(dbKey, new SqlQuery("AuthZ_spGetGroupsByRoles", CommandType.StoredProcedure, sqlParameters)))
            {
                //Store the SG in a property and create the RoleEntry.
                RelatedSGProperty sGProperty = new RelatedSGProperty(columns[1].ToString());
                this.Add(columns[0].ToString(), sGProperty, true);
            }
        }

        #endregion

        #region GetEntryProperties
        /// <summary>
        /// Loops through each entry and gets all the users of the relatedSG from AD.
        /// Also gets the child roles from database.
        /// descendant security groups.
        /// </summary>
        private void GetEntryProperties()
        {
            foreach (RoleMembersEntry roleMember in this)
                roleMember.LoadProperties();
        }

        #endregion

        #region LoadRolesAndMembers
        /// <summary>
        /// Loads a collection of roles and associated security groups from the application database.
        /// Then loads Person Data from AD.
        /// </summary>
        /// <param name="roleFilters">A collection of roles to select from database.</param>
        /// <param name="authenticatedUser">The alias of the user to assign to the database updates.</param>
        /// <param name="dbKey">Name of the configuration key to use to get database connection string.</param>
        /// <returns>Collection of roles that were mapped.</returns>
        public Collection<string> LoadRolesAndMembers(Collection<string> roleFilters, string authenticatedUser, string dbKey)
        {
            this.roleFilters = roleFilters;
            //Clear out any collections loaded in this object.
            this.ClearAll();

            this.GetGroupsByRolesDB(dbKey);

            //Gets all User members from Active Directory.
            this.GetEntryProperties();
            return this.LoadUserRoleMappingsIntoDB(authenticatedUser, dbKey);
        }

        #endregion

        #region LoadUserRoleMappingsIntoDB
        /// <summary>
        /// Inserts a new set of mappings from users to roles into the db.
        /// </summary>
        /// <param name="authenticatedUser">The alias of the user to assign to the database updates.</param>
        /// <param name="dbKey">Name of the configuration key to use to get database connection string.</param>
        /// <remarks>For each role, existing user mappings are deleted.  Any orphaned users are deleted
        /// from db.  New users are inserted if they don't already exist.  Mappings for each role are created 
        /// based on the associated Security Group(s) in the group to mapping table.  All users found in AD for
        /// those Security Groups (and descendant groups) will be mapped to the role.
        /// </remarks>
        /// <returns>Collection of roles that were mapped.</returns>
        private Collection<string> LoadUserRoleMappingsIntoDB(string authenticatedUser, string dbKey)
        {
            Collection<string> roles = new Collection<string>();
            Collection<SqlParameter> sqlParameters;
            Collection<SqlQuery> sqlQueries = new Collection<SqlQuery>();

            foreach (RoleMembersEntry roleMember in this)
            {
                roles.Add(roleMember.Name);

                sqlParameters = new Collection<SqlParameter>();
                //sqlQueries.Clear();

                //Create a new role parameter for this role.
                SqlParameter sqlParameter = new SqlParameter("@role", SqlDbType.NVarChar, 50, roleMember.Name);
                sqlParameters.Add(sqlParameter);

                //First, create the query to remove all user mappings to this role.
                sqlQueries.Add(new SqlQuery("AuthZ_spDeleteAllUsersForRole", CommandType.StoredProcedure, sqlParameters));

                foreach (PersonDataProperty personData in roleMember.GetProperty(AuthZGlobals.PropertyType.PersonData))
                {
                    //Create a new set of parameters for this user.  Add the role parameter to the beginning.
                    sqlParameters = new Collection<SqlParameter>();
                    sqlParameters.Add(sqlParameter);

                    //Tack on each data string from person to the parameter string.
                    sqlParameters.Add(new SqlParameter("@displayName", SqlDbType.NVarChar, 100, personData[0]));
                    sqlParameters.Add(new SqlParameter("@personnelNumber", SqlDbType.Int, 0, Int32.Parse(personData[1])));
                    sqlParameters.Add(new SqlParameter("@sAMAccountName", SqlDbType.NVarChar, 50, personData[2]));

                    //Add the alias of the user making this update.
                    sqlParameters.Add(new SqlParameter("@alias", SqlDbType.NVarChar, 50, authenticatedUser));

                    //Create the query to add the mapping to this role for this person.
                    sqlQueries.Add(new SqlQuery("AuthZ_spInsertUserToRole", CommandType.StoredProcedure, sqlParameters));
                }

                //Execute all the queries in a single transaction.
                //AuthZDataAccess.ExecuteNonQueryTransaction(dbKey, sqlQueries);
            }

            sqlParameters = new Collection<SqlParameter>();
            sqlParameters.Add(new SqlParameter("@alias", SqlDbType.NVarChar, 50, authenticatedUser));
            //Create the query to update the denormalized user to role map table.
            sqlQueries.Add(new SqlQuery("AuthZ_spUpdateAuthZUserRole", CommandType.StoredProcedure, sqlParameters));

            //Execute all the queries in a single transaction.
            AuthZDataAccess.ExecuteNonQueryTransaction(dbKey, sqlQueries);

            return roles;
        }

        #endregion

        #endregion

    }
}
